package jdtprototype;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.epsilon.eol.parse.Eol_EolParserRules.returnStatement_return;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;

/**
 * Manipulates Java source using JDT
 * @author Cheng Yun
 *
 */
public class JdtManipulator {
	IProject[] projects;
	
	/**
	 * Constructor: default 
	 */
	public JdtManipulator(){
		
	}
	
	/**
	 * Constructor: initialise the projects with the parameter passed in
	 * @param projects java projects to be manipulated
	 */
	public JdtManipulator(IProject[] projects){
		this.projects = projects;
	}

	/**
	 * Constructor: read all projects in the workspace
	 * @param str
	 */
	public JdtManipulator(String str) {
		IWorkspace workspace = ResourcesPlugin.getWorkspace();
		IWorkspaceRoot root = workspace.getRoot();
		// Get all projects in the workspace
		projects = root.getProjects();
	}
		
	/**
	 * Converts a IProject to IJavaProject if it has a java nature
	 * @param IProject
	 * @return IJavaProject
	 * @throws CoreException
	 */
	public static IJavaProject getIJavaProject(IProject project) throws CoreException{
		IJavaProject javaProject;
		//the project should be a open Java project
		if (project.isNatureEnabled("org.eclipse.jdt.core.javanature")&&project.isOpen()){
			javaProject = JavaCore.create(project);
			return javaProject;
		}
		return null;
	}
	
	/**
	 * Converts an array of IProject to an array of IJavaProject, skipping elements which are not of Java nature
	 * @param proejcts
	 * @return
	 * @throws CoreException
	 */
	public static IJavaProject [] getIJavaProjects(IProject [] proejcts) throws CoreException{
		List <IJavaProject> sourceProjects = new ArrayList<IJavaProject>();
		for(IProject project : proejcts){
			if(getIJavaProject( project)!= null){
				sourceProjects.add(getIJavaProject(project));
			}
		}
		IJavaProject [] javaProjects = sourceProjects.toArray(new IJavaProject[sourceProjects.size()]);
		
		return javaProjects;	
	}
	
	/**
	 * returns all packages that contains source code in a java project
	 * 
	 * @param javaProject
	 *            the javaProject to be parsed;
	 * @param isSource
	 *            if true,  return the java source packages only; otherwise, return all packages.
	 * @return an array of packages containing Java source
	 * @throws CoreException
	 */
	public static IPackageFragment[] getIPackageFragments(IJavaProject javaProject,
			boolean isSource) throws CoreException {
		IPackageFragment[] packages = javaProject.getPackageFragments();
		List <IPackageFragment> sourcePackages = new ArrayList<IPackageFragment>();
		
		if (isSource) {// only package containing source code should be returned
			for (IPackageFragment packageFragment : packages) {
				//if this is a java source package
				if (packageFragment.getKind() == IPackageFragmentRoot.K_SOURCE) {
					sourcePackages.add(packageFragment);
				}
			}
			packages = sourcePackages.toArray(new IPackageFragment[sourcePackages.size()]);
		}else {
			for (IPackageFragment packageFragment : packages) {
					sourcePackages.add(packageFragment);
			}
			packages = sourcePackages.toArray(new IPackageFragment[sourcePackages.size()]);
		}

		return packages;
	}

	/**
	 * return all packages that contains source code in a java project
	 * 
	 * @param project
	 *            the IProject to be parsed;
	 * @param isSource
	 *            a flag indicating if the returned IPackageFragment should be
	 *            java source package only.
	 * @return an array of packages containing Java source
	 * @throws CoreException
	 */
	public static IPackageFragment[] getIPackageFragments(IProject project,
			boolean isSource) throws CoreException {
		if (project.hasNature("org.eclipse.jdt.core.javanature")) {
			IJavaProject javaProject = getIJavaProject(project);
			IPackageFragment[] packages = getIPackageFragments(javaProject,
					isSource);
			return packages;
		}

		return null;
	}	

	/**
	 * returns all ICompilationUnits in a package
	 * 
	 * @param packageFragment
	 * @return
	 * @throws JavaModelException
	 */
	public static ICompilationUnit[] getICompilationUnits(
		IPackageFragment packageFragment) throws JavaModelException {
		ICompilationUnit[] iUnit = null;
		// determine if it is a java source code package
		if (packageFragment.getKind() == IPackageFragmentRoot.K_SOURCE) {
			iUnit = packageFragment.getCompilationUnits();
		}
		return iUnit;
	}

	/**
	 * creates a AST tree based on the ICompilationUnit passed in
	 * 
	 * @param unit
	 * @return
	 */
	public static CompilationUnit parse(ICompilationUnit iUnit) {
		ASTParser parser = ASTParser.newParser(AST.JLS3);
		parser.setKind(ASTParser.K_COMPILATION_UNIT);
		parser.setSource(iUnit);
		parser.setResolveBindings(true);
		return (CompilationUnit) parser.createAST(null); // parse
	}
	
	/**
	 * return all CompilationUnits AST nodes of all the projects to be manipulated
	 * @return
	 * @throws CoreException
	 */
	public List <CompilationUnit> getCompilationUnits() throws CoreException {
		List <CompilationUnit> units = new ArrayList<CompilationUnit>();
		//traverse all project that are open
		for (IProject project : projects) {
			// determin if the project is open
			if (project.isOpen()) {
				if (project.hasNature("org.eclipse.jdt.core.javanature")){
					IJavaProject javaProject = getIJavaProject(project);
				IPackageFragment[] packageFragments = getIPackageFragments(javaProject,true);
				//traverse all package fragments
				for(IPackageFragment packageFragment : packageFragments	){
					ICompilationUnit [] iunit = getICompilationUnits (packageFragment);
					//traverse all ICompilationUnits
					for(int i=0; i<iunit.length;i++){
						units.add(parse(iunit[i]));
					}
				}
			}
			}
		}

		return units;
	}

	/**
	 * return all CompilationUnits AST nodes in the given package
	 * @return
	 * @throws CoreException
	 */
	public static CompilationUnit [] getCompilationUnits(IPackageFragment packageFragment) throws CoreException{
		if(packageFragment != null){
			ICompilationUnit [] iunit = getICompilationUnits (packageFragment);
			CompilationUnit [] units = new CompilationUnit[iunit.length];
			//traverse all ICompilationUnits
			for(int i=0; i<iunit.length;i++){
				units[i]=parse(iunit[i]);
			}
			return units;
		}
		return null;
	}
	
	/**
	 * return all CompilationUnits AST nodes in the given project
	 * 
	 * @param javaProject: A IJavaProject to be parsed
	 * @return a list of compilation units
	 * @throws CoreException
	 */
	public static List <CompilationUnit> getCompilationUnits(IJavaProject javaProject) throws CoreException {
		if(javaProject != null){
			List <CompilationUnit> units = new ArrayList<CompilationUnit>();
			for(IPackageFragment packageFragment : getIPackageFragments(javaProject,true)){
				for(CompilationUnit unit : getCompilationUnits(packageFragment)){
					units.add(unit);
				}
			}
			return units;
		}
		return null;
	}

	public IType[] getITypes(ICompilationUnit unit) throws JavaModelException {
		IType[] types = unit.getAllTypes();
		return types;
	}
	
	/**
	 * returns the package declaration AST nodes to which the given
	 * compilation belongs
	 * 
	 * @param unit CompilationUnit
	 * @return PackageDeclaration
	 */
	public static PackageDeclaration getPackageDeclaration(CompilationUnit unit) {

		return unit.getPackage();
	}
	
	/**
	 * returns a list containing the class declaration AST nodes in the given
	 * compilation unit
	 * 
	 * @param unit CompilationUnit
	 * @return an array of TypeDeclaration
	 */
	public static TypeDeclaration[] getTypeDeclaration(CompilationUnit unit) {
		if(unit != null){
			Visitor cVisitor = new Visitor();
			unit.accept(cVisitor);
			TypeDeclaration[] classes = new TypeDeclaration[cVisitor
					.getClasses().size()];

			for (int i = 0; i < cVisitor.getClasses().size(); i++) {
				classes[i] = cVisitor.getClasses().get(i);
			}

			return classes;
		}
		return null;
	}
	
	/**
	 * returns a list containing the class declaration AST nodes in the given
	 * project
	 * @param project
	 * @return a list of TypeDeclaration
	 * @throws CoreException
	 */
	public static List<TypeDeclaration> getTypeDeclaration(IJavaProject javaProject) throws CoreException{
		if(javaProject!=null){
			List <TypeDeclaration> typeDeclarations = new ArrayList<TypeDeclaration>();
			for(CompilationUnit unit : getCompilationUnits(javaProject)){
				typeDeclarations.addAll(Arrays.asList(getTypeDeclaration(unit)));
			}
			return typeDeclarations;
		}

		return null;
	}

	/**
	 * returns a list containing the class declaration AST nodes in the given
	 * IPackageFragment
	 * @param packageFragment IPackageFragment
	 * @return a list containing the class declaration AST nodes
	 * @throws CoreException
	 */
	public static List<TypeDeclaration> getTypeDeclaration(IPackageFragment packageFragment) throws CoreException{
		if(packageFragment!=null){
			List <TypeDeclaration> typeDeclarations = new ArrayList<TypeDeclaration>();
			for(CompilationUnit unit : getCompilationUnits(packageFragment)){
				typeDeclarations.addAll(Arrays.asList(getTypeDeclaration(unit)));
			}
			return typeDeclarations;
		}

		return null;
	}
	
	/**
	 * returns a list containing the method declaration AST nodes in the given
	 * compilation unit
	 * 
	 * @param unit CompilationUnit
	 * @return a list of method declaration AST nodes
	 */
	public static MethodDeclaration[] getMethodDeclaration(CompilationUnit unit) {
		Visitor mVisitor = new Visitor();
		unit.accept(mVisitor);
		MethodDeclaration[] methods = new MethodDeclaration[mVisitor
				.getMethods().size()];

		for (int i = 0; i < mVisitor.getMethods().size(); i++) {
			methods[i] = mVisitor.getMethods().get(i);
		}

		return methods;
	}
	
	/**
	 * returns a list containing the method declaration AST nodes in the given
	 * IPackageFragment
	 * 
	 * @param unit
	 * @return a list containing the method declaration AST nodes
	 * @throws CoreException 
	 */
	public static List <MethodDeclaration> getMethodDeclaration(IPackageFragment packageFragment) throws CoreException {
		List <MethodDeclaration> methods = new ArrayList<MethodDeclaration>();
		
		for(CompilationUnit unit : getCompilationUnits(packageFragment)){
			Visitor mVisitor = new Visitor();
			unit.accept(mVisitor);
			methods.addAll(mVisitor.getMethods());
		}

		return methods;
	}
	
	/**
	 * returns a list containing the method declaration AST nodes in the given
	 * TypeDeclaration
	 * 
	 * @param typeDeclaration TypeDeclaration
	 * @return a list of method declaration AST nodes
	 */
	public static MethodDeclaration[] getMethodDeclaration(TypeDeclaration typeDeclaration) {

		return typeDeclaration.getMethods();
	}

	/**
	 * returns a list containing the variable declaration statement AST nodes in
	 * the given compilation unit
	 * 
	 * @param unit
	 * @return
	 */
	public static VariableDeclarationFragment[] getVariableDeclarationStatement(
			CompilationUnit unit) {
		Visitor vVisitor = new Visitor();
		unit.accept(vVisitor);
		VariableDeclarationFragment[] variables = new VariableDeclarationFragment[vVisitor
				.getVariables().size()];

		for (int i = 0; i < vVisitor.getVariables().size(); i++) {
			variables[i] = vVisitor.getVariables().get(i);
		}

		return variables;
	}

	/**
	 * Visitor for retrieving AST node information.
	 * 
	 */
	static class Visitor extends ASTVisitor {
		List<TypeDeclaration> classes = new ArrayList<TypeDeclaration>();
		List<MethodDeclaration> methods = new ArrayList<MethodDeclaration>();
		List<VariableDeclarationFragment> variables = new ArrayList<VariableDeclarationFragment>();

		  @Override
		  public boolean visit(TypeDeclaration node) {
			  classes.add(node);
		    return super.visit(node);
		  }

		  public List<TypeDeclaration> getClasses() {
		    return classes;
		  }

		@Override
		public boolean visit(MethodDeclaration node) {
			methods.add(node);
			return super.visit(node);
		}

		public boolean visit(VariableDeclarationFragment node) {
			variables.add(node);
			return false;
		}

		public List<MethodDeclaration> getMethods() {
			return methods;
		}

		public List<VariableDeclarationFragment> getVariables() {
			return variables;
		}
	}
}
